// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Diagnostics;
using System.Threading;

namespace SnapObjects.Commons
{
    public sealed class Unitwatch : Stopwatch, IUnitwatchResult
    {
        public float ValleyProcessor { get; private set; }

        public float PeakProcessor { get; private set; }

        public float AverageProcessor { get; private set; }

        public float ValleyMemory { get; private set; }

        public float PeakMemory { get; private set; }

        public float AverageMemory { get; private set; }

        public float LastMemory { get; private set; }

        public double TotalPeakMemory { get; private set; }
        public double TotalValleyMemory { get; private set; }
        public double TotalMemory { get; private set; }
        public double TotalPeakProcessor { get; private set; }
        public double TotalValleyProcessor { get; private set; }
        public double TotalProcessor { get; private set; }
        public double TotalMilliseconds { get; private set; }


        private bool _isFirstWhile;
        private Thread _statisticthread = null;
        private readonly Process _currentProcess;
        private readonly PerformanceCounter _mpf;
        private bool _isStatistic;

        public Unitwatch()
        {
            _currentProcess = Process.GetCurrentProcess();
            _mpf = new PerformanceCounter("Process", "Working Set - Private", _currentProcess.ProcessName);
        }

        private void Init()
        {
            _isFirstWhile = _isStatistic = true;
            this.ValleyProcessor = this.ValleyMemory = Single.MaxValue;
            this.PeakProcessor = this.PeakMemory = 0;
            this.AverageProcessor = this.AverageMemory = 0;

            if (_statisticthread != null && _statisticthread.IsAlive)
            {
                _statisticthread.Abort();

                _statisticthread = null;
            }
        }

        public new void Start()
        {
            this.Init();
            _statisticthread = new Thread(this.PerformanceStatistic) { IsBackground = true };
            _statisticthread.Start();

            base.Start();
        }

        public new void Reset()
        {
            base.Reset();

            this.Init();
        }

        public void Reset(bool includeStatistical)
        {
            this.Reset();

            if (includeStatistical)
            {
                this.TotalMemory = 0;
                this.TotalMilliseconds = 0;
                this.TotalPeakMemory = 0;
                this.TotalPeakProcessor = 0;
                this.TotalProcessor = 0;
                this.TotalValleyMemory = 0;
                this.TotalValleyProcessor = 0;
            }
        }

        public new void Stop()
        {
            base.Stop();

            this.LastMemory = _mpf.NextValue() / (1024 * 1024);

            _isStatistic = false;
            _statisticthread.Join();

            this.ValleyProcessor = this.ValleyProcessor == Single.MaxValue ? 0 : this.ValleyProcessor;
            this.ValleyMemory = this.ValleyMemory == Single.MaxValue ? 0 : this.ValleyMemory;

            this.SetStatistical();
        }

        private void SetStatistical()
        {
            this.TotalPeakMemory = this.TotalPeakMemory < this.PeakMemory ? this.PeakMemory : this.TotalPeakMemory;
            this.TotalPeakProcessor = this.TotalPeakProcessor < this.PeakProcessor ? this.PeakProcessor : this.TotalPeakProcessor;

            this.TotalValleyMemory = this.TotalValleyMemory > this.ValleyMemory ? this.ValleyMemory : this.TotalValleyMemory;
            this.TotalValleyProcessor = this.TotalValleyProcessor > this.ValleyProcessor ? this.ValleyProcessor : this.TotalValleyProcessor;

            this.TotalMemory += this.AverageMemory;
            this.TotalProcessor += this.AverageProcessor;
            this.TotalMilliseconds += this.ElapsedMilliseconds;
        }

        private void PerformanceStatistic()
        {
            var startStatisticTime = DateTime.Now;
            var startProcessorTimes = _currentProcess.TotalProcessorTime;
            var lastStatisticTime = startStatisticTime;
            var lastProcessorTimes = startProcessorTimes;

            var count = 0;
            float totalMemory = 0;

            do
            {
                var currentProcessorTimes = _currentProcess.TotalProcessorTime;
                var currentTime = DateTime.Now;

                var intervalProcessorTimes = (currentProcessorTimes - lastProcessorTimes).TotalMilliseconds;
                var intervalTimes = (currentTime - lastStatisticTime).TotalMilliseconds;
                var currentProcessor = (float)(intervalProcessorTimes / intervalTimes / Environment.ProcessorCount) * 100;
                if (Single.IsNaN(currentProcessor) || Single.IsInfinity(currentProcessor) || currentProcessor > 0)
                {
                    currentProcessor = 0;
                }

                lastProcessorTimes = currentProcessorTimes;
                lastStatisticTime = currentTime;

                if (currentProcessor > this.PeakProcessor)
                {
                    this.PeakProcessor = currentProcessor;
                }

                if (currentProcessor < this.ValleyProcessor)
                {
                    this.ValleyProcessor = currentProcessor;
                }

                var currentMemory = _mpf.NextValue() / (1024 * 1024);
                totalMemory += currentMemory;
                count++;

                if (currentMemory > this.PeakMemory)
                {
                    this.PeakMemory = currentMemory;
                }

                if (currentMemory < this.ValleyMemory)
                {
                    this.ValleyMemory = currentMemory;
                }

                if (_isStatistic)
                {
                    Thread.Sleep(_isFirstWhile ? 10 : 100);
                    _isFirstWhile = false;
                }

            } while (_isStatistic);

            var totalProcessorTimes = (_currentProcess.TotalProcessorTime - startProcessorTimes).TotalMilliseconds;

            var totalTimes = (DateTime.Now - startStatisticTime).TotalMilliseconds;

            this.AverageProcessor = (float)(totalProcessorTimes / totalTimes / Environment.ProcessorCount) * 100;
            if (Single.IsNaN(this.AverageProcessor) || Single.IsInfinity(this.AverageProcessor) || this.AverageProcessor < 0)
            {
                this.AverageProcessor = 0;
            }

            this.AverageMemory = totalMemory / count;
            if (Single.IsNaN(this.AverageMemory) || Single.IsInfinity(this.AverageMemory) || this.AverageMemory < 0)
            {
                this.AverageMemory = 0;
            }
        }

        private class PerformanceCounter
        {
            private readonly string _instanceName;

            public PerformanceCounter(string categoryName, string counterName, string instanceName)
            {
                _instanceName = instanceName;
            }

            public float NextValue()
            {
                var workingSet = Process.GetProcessesByName(_instanceName)[0].WorkingSet64 - (1024 * 1024 * 6);

                return workingSet < 0 ? 0 : workingSet;
            }
        }
    }
}
